use rbatis::crud::{Skip, CRUD};
use rbatis::rbatis::Rbatis;
use uhlc::HLC;
use uuid::Uuid;

use chrono::prelude::*;

use crate::dbs::sqlite::Peer;
use crate::features::ddd::Aggregate;
use crate::features::events::modelsdbo::Event;
use crate::features::events::modelsdbo::EventDBO;
use crate::features::gnode::KV;
use crate::features::tasks::modelsgqo::NewTask;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ProductConfig {
  size: usize,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ProductEventReserved {
  quantity: usize,
  timestamp: usize,
  sku: String,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ProductEventReleased {
  quantity: usize,
  timestamp: usize,
  sku: String,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ProductEventShipped {
  quantity: usize,
  timestamp: usize,
  sku: String,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum ProductEvent {
  Reserved(ProductEventReserved),
  Released(ProductEventReleased),
  Shipped(ProductEventShipped),
}

impl ProductEvent {
  fn reserved(sku: &str, qty: usize) -> ProductEvent {
    ProductEvent::Reserved(ProductEventReserved {
      quantity: qty,
      sku: sku.to_owned(),
      timestamp: 1,
    })
  }

  fn released(sku: &str, qty: usize) -> ProductEvent {
    ProductEvent::Released(ProductEventReleased {
      quantity: qty,
      sku: sku.to_owned(),
      timestamp: 1,
    })
  }

  fn shipped(sku: &str, qty: usize) -> ProductEvent {
    ProductEvent::Shipped(ProductEventShipped {
      quantity: qty,
      sku: sku.to_owned(),
      timestamp: 1,
    })
  }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
struct ProductAggregate {
  snapshot_at: String,
  qty_on_hand: usize,
  sku: String,
}

impl ProductAggregate {
  // initialize a ProductAggregate , typically after loading data from db
  fn init(sku: &str, initial_quantity: usize) -> ProductAggregate {
    ProductAggregate {
      snapshot_at: "abcd-example-timestamp-for-testing".into(),
      qty_on_hand: initial_quantity,
      sku: sku.to_owned(),
    }
  }

  // command reserve_quantity
  fn reserve_quantity(&self, qty: usize) -> Result<Vec<ProductEvent>, String> {
    if qty > self.qty_on_hand {
      let msg = format!(
        "Cannot reserve more than on hand quantity ({})",
        self.qty_on_hand
      );
      Err(msg)
    } else if self.snapshot_at == "" {
      Err(
        "Cannot apply a command to an un-initialized aggregate. Did you forget something?"
          .to_owned(),
      )
    } else {
      Ok(vec![ProductEvent::reserved(&self.sku, qty)])
    }
  }

  // command reserve_quantity
  fn release_quantity(&self, qty: usize) -> Result<Vec<ProductEvent>, String> {
    Ok(vec![ProductEvent::released(&self.sku, qty)])
  }

  // command ship_quantity
  fn ship_quantity(&self, qty: usize) -> Result<Vec<ProductEvent>, String> {
    Ok(vec![ProductEvent::shipped(&self.sku, qty)])
  }

  fn quantity(&self) -> usize {
    self.qty_on_hand
  }
}

impl Aggregate for ProductAggregate {
  type ID = String;
  // https://medium.com/capital-one-tech/event-sourcing-with-aggregates-in-rust-4022af41cf67
  // First, you’ll notice that I’m using an associated type to indicate that
  // the type of Items this aggregate processes are ProductEvent variants.
  // I can then use destructuring in my pattern match to pull out just the
  // qty field of the ProductEventData struct in order to produce the newly
  // calculated aggregate.
  type ItemEvent = ProductEvent;
  type Snapshot = ProductAggregate;
  type Config = ProductConfig;


  fn init(snapshot: &ProductAggregate, events: &Vec<ProductEvent>,config:&ProductConfig) -> ProductAggregate {
    ProductAggregate {
      snapshot_at: "".into(),
      qty_on_hand: 123,
      sku: "sku123".into(),
    }
  }

  fn apply(&self, evt: &ProductEvent) -> ProductAggregate {
    ProductAggregate {
      snapshot_at: "evt_timestamp.to_string()".into(),
      qty_on_hand: match evt {
        &ProductEvent::Released(ProductEventReleased { quantity: qty, .. }) => {
          self.qty_on_hand.checked_add(qty).unwrap_or(0)
        }
        &ProductEvent::Reserved(ProductEventReserved { quantity: qty, .. }) => {
          self.qty_on_hand.checked_sub(qty).unwrap_or(0)
        }
        _ => self.qty_on_hand,
      },
      sku: self.sku.clone(),
    }
  }

  fn get_id(&self) -> &String {
    &self.sku
  }
  fn get_snapshot(&self) -> &Self::Snapshot {
    &self
  }
}
